package br.boicotando.server;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.Stack;
import java.util.Timer;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

import org.hibernate.SessionFactory;
import org.hibernate.cfg.AnnotationConfiguration;

import br.boicotando.dominio.Carrinho;
import br.boicotando.dominio.Categoria;
import br.boicotando.dominio.Cliente;
import br.boicotando.dominio.Endereco;
import br.boicotando.dominio.ItemCarrinho;
import br.boicotando.dominio.Municipio;
import br.boicotando.dominio.Pedido;
import br.boicotando.dominio.Produto;
import br.boicotando.dominio.Setor;
import br.boicotando.dominio.SubCategoria;
import br.boicotando.dominio.UnidadeFederativa;
import br.boicotando.persistencia.ECarrinho;
import br.boicotando.persistencia.ECategoria;
import br.boicotando.persistencia.ECliente;
import br.boicotando.persistencia.EEndereco;
import br.boicotando.persistencia.EItemCarrinho;
import br.boicotando.persistencia.EMunicipio;
import br.boicotando.persistencia.EPedido;
import br.boicotando.persistencia.EProduto;
import br.boicotando.persistencia.ESetor;
import br.boicotando.persistencia.ESubCategoria;
import br.boicotando.persistencia.EUnidadeFederativa;

public class Bau implements ServletContextListener {

    private static final Logger logger = Logger.getLogger("br.boicotando.server.BoicotandoCTX");
    
    public static final String PARAM_URL_SETOR_CATEGORIA_PRODUTO_XML = "url-setor-categoria-produto-xml";
	private static SessionFactory fabricaSessao;
	private static URL URLSetorCategoriaProdutoXML;
	private static Timer timer;

	public static class Injecao {
		private Injetor injetor;
		private Object objA;
		private Object objB;
		
		private Injecao(Injetor injetor, Object objA) {
			this.injetor = injetor;
			this.objA = objA;
		}
		
		@SuppressWarnings("unused")
		private Object getObjA() {
			return objA;
		}
		
		private Object getObjB() {
			return objB;
		}
		
		private void aplica(ESetor eSetor, Setor dSetor) {
			ECategoria eCategorias[] = new ECategoria[0];
			eCategorias = eSetor.getCategorias(eCategorias);
			dSetor.setId(eSetor.getId());
			dSetor.setNome(eSetor.getNome());
			if (dSetor instanceof SubCategoria && eSetor instanceof ESubCategoria) {
				SubCategoria dSubCategoria = (SubCategoria) dSetor;
				ESubCategoria eSubCategoria = (ESubCategoria) eSetor;
				if (eSubCategoria.getCategoria() == null) {
					dSubCategoria.setCategoria(null);
				} else {
					dSubCategoria.setCategoria((Categoria) injetor.injetaA(eSubCategoria.getCategoria()).emB(new Categoria()));
				}
			} else if (dSetor instanceof Categoria && eSetor instanceof ECategoria) {
				Categoria dCategoria = (Categoria) dSetor;
				ECategoria eCategoria = (ECategoria) eSetor;
				ESubCategoria eSubCategorias[] = new ESubCategoria[0];
			    eSubCategorias = eCategoria.getSubCategorias(eSubCategorias);
				if (eCategoria.getSetor() == null) {
					dCategoria.setSetor(null);
				} else {
					dCategoria.setSetor((Setor) injetor.injetaA(eCategoria.getSetor()).emB(new Setor()));
				}
				dCategoria.removeSubcategorias();
				for (int i = 0; i < eSubCategorias.length; ++i) {
					dCategoria.adicionaSubCategoria((SubCategoria) injetor.injetaA(eSubCategorias[i]).emB(new SubCategoria()));
				}
			} else {
				dSetor.removeCategorias();
				for (int i = 0; i < eCategorias.length; ++i) {
					dSetor.adicionaCategoria((Categoria) injetor.injetaA(eCategorias[i]).emB(new Categoria()));
				}			
			}
		}
		
		private void aplica(Setor dSetor, ESetor eSetor) {
			Categoria dCategorias[] = new Categoria[0];
			dCategorias = dSetor.getCategorias(dCategorias);
			eSetor.setId(dSetor.getId());
			eSetor.setNome(dSetor.getNome());
			if (eSetor instanceof ESubCategoria && dSetor instanceof SubCategoria) {
				ESubCategoria eSubCategoria = (ESubCategoria) eSetor;
				SubCategoria dSubCategoria = (SubCategoria) dSetor;
				if (dSubCategoria.getCategoria() == null) {
					eSubCategoria.setCategoria(null);
				} else {
					eSubCategoria.setCategoria((ECategoria) injetor.injetaA(dSubCategoria.getCategoria()).emB(new ECategoria()));
				}
			} else if (eSetor instanceof ECategoria && dSetor instanceof Categoria) {
				ECategoria eCategoria = (ECategoria) eSetor;
				Categoria dCategoria = (Categoria) dSetor;
				SubCategoria dSubCategorias[] = new SubCategoria[0];
			    dSubCategorias = dCategoria.getSubCategorias(dSubCategorias);
				if (dCategoria.getSetor() == null) {
					eCategoria.setSetor(null);
				} else {
					eCategoria.setSetor((ESetor) injetor.injetaA(dCategoria.getSetor()).emB(new ESetor()));
				}
				eCategoria.removeSubcategorias();
				for (int i = 0; i < dSubCategorias.length; ++i) {
					eCategoria.adicionaSubCategoria((ESubCategoria) injetor.injetaA(dSubCategorias[i]).emB(new ESubCategoria()));
				}
			} else {
				eSetor.removeCategorias();
				for (int i = 0; i < dCategorias.length; ++i) {
					eSetor.adicionaCategoria((ECategoria) injetor.injetaA(dCategorias[i]).emB(new ECategoria()));
				}							
			}	
		}
		
		private void aplica(EProduto eProduto, Produto dProduto) {
			dProduto.setId(eProduto.getId());
			dProduto.setDescricao(eProduto.getDescricao());
			dProduto.setValorUnitario(eProduto.getValorUnitario());
			dProduto.setEnderecoImagem(eProduto.getEnderecoImagem());
			if (eProduto.getSetor() == null) {
				dProduto.setSetor(null);
			} else {
				dProduto.setSetor((Setor) injetor.injetaA(eProduto.getSetor()).emB(new Setor()));
			}
		}

		private void aplica(Produto dProduto, EProduto eProduto) {
			eProduto.setId(dProduto.getId());
			eProduto.setDescricao(dProduto.getDescricao());
			eProduto.setValorUnitario(dProduto.getValorUnitario());
			eProduto.setEnderecoImagem(dProduto.getEnderecoImagem());
			if (dProduto.getSetor() == null) {
				eProduto.setSetor(null);
			} else {
				eProduto.setSetor((ESetor) injetor.injetaA(dProduto.getSetor()).emB(new ESetor()));
			}
		}
		
		private void aplica(EUnidadeFederativa eUF, UnidadeFederativa dUF) {
			dUF.setId(eUF.getId());
			dUF.setNome(eUF.getNome());
			dUF.setSigla(eUF.getSigla());
		}

		private void aplica(UnidadeFederativa dUF, EUnidadeFederativa eUF) {
			eUF.setId(dUF.getId());
			eUF.setNome(dUF.getNome());
			eUF.setSigla(dUF.getSigla());
		}
		
		private void aplica(EMunicipio eMunicipio, Municipio dMunicipio) {
			dMunicipio.setId(eMunicipio.getId());
			dMunicipio.setNome(eMunicipio.getNome());
			if (eMunicipio.getUnidadeFederativa() == null) {
				dMunicipio.setUnidadeFederativa(null);
			} else {
				dMunicipio.setUnidadeFederativa((UnidadeFederativa) injetor.injetaA(eMunicipio.getUnidadeFederativa()).emB(new UnidadeFederativa()));
			}
		}

		private void aplica(Municipio dMunicipio, EMunicipio eMunicipio) {
			eMunicipio.setId(dMunicipio.getId());
			eMunicipio.setNome(dMunicipio.getNome());
			if (dMunicipio.getUnidadeFederativa() == null) {
				eMunicipio.setUnidadeFederativa(null);
			} else {
				eMunicipio.setUnidadeFederativa((EUnidadeFederativa) injetor.injetaA(dMunicipio.getUnidadeFederativa()).emB(new EUnidadeFederativa()));
			}
		}
		
		private void aplica(EEndereco eEndereco, Endereco dEndereco) {
			dEndereco.setLogradouro(eEndereco.getLogradouro());
			dEndereco.setNumero(eEndereco.getNumero());
			dEndereco.setCep(eEndereco.getCep());
			dEndereco.setBairro(eEndereco.getBairro());
			dEndereco.setComplemento(eEndereco.getComplemento());
			if (eEndereco.getMunicipio() == null) {
				dEndereco.setMunicipio(null);
			} else {
				dEndereco.setMunicipio((Municipio) injetor.injetaA(eEndereco.getMunicipio()).emB(new Municipio()));
			}
		}

		private void aplica(Endereco dEndereco, EEndereco eEndereco) {
			eEndereco.setLogradouro(dEndereco.getLogradouro());
			eEndereco.setNumero(dEndereco.getNumero());
			eEndereco.setCep(dEndereco.getCep());
			eEndereco.setBairro(dEndereco.getBairro());
			eEndereco.setComplemento(dEndereco.getComplemento());
			if (dEndereco.getMunicipio() == null) {
				eEndereco.setMunicipio(null);
			} else {
				eEndereco.setMunicipio((EMunicipio) injetor.injetaA(dEndereco.getMunicipio()).emB(new EMunicipio()));
			}
		}
		
		private void aplica(ECliente eCliente, Cliente dCliente) {
			dCliente.setId(eCliente.getId());
			dCliente.setNome(eCliente.getNome());
			dCliente.setSobrenome(eCliente.getSobrenome());
			dCliente.setCPF(eCliente.getCPF());
			dCliente.setDataNascimento(eCliente.getDataNascimento());
			dCliente.setSexo(eCliente.getSexo());
			if (eCliente.getEndereco() == null) {
				dCliente.setEndereco(null);
			} else {
				dCliente.setEndereco((Endereco) injetor.injetaA(eCliente.getEndereco()).emB(new Endereco()));
			}
			dCliente.setNumeroCelular(eCliente.getNumeroCelular());
			dCliente.setNumeroFone(eCliente.getNumeroFone());
			dCliente.setNumeroFone2(eCliente.getNumeroFone2());
			dCliente.setEmail(eCliente.getEmail());
			dCliente.setSenha(eCliente.getSenha());		
		}

		private void aplica(Cliente dCliente, ECliente eCliente) {
			eCliente.setId(dCliente.getId());
			eCliente.setNome(dCliente.getNome());
			eCliente.setSobrenome(dCliente.getSobrenome());
			eCliente.setCPF(dCliente.getCPF());
			eCliente.setDataNascimento(dCliente.getDataNascimento());
			eCliente.setSexo(dCliente.getSexo());
			if (dCliente.getEndereco() == null) {
				eCliente.setEndereco(null);
			} else {
				eCliente.setEndereco((EEndereco) injetor.injetaA(dCliente.getEndereco()).emB(new EEndereco()));
			}
			eCliente.setNumeroCelular(dCliente.getNumeroCelular());
			eCliente.setNumeroFone(dCliente.getNumeroFone());
			eCliente.setNumeroFone2(dCliente.getNumeroFone2());
			eCliente.setEmail(dCliente.getEmail());
			eCliente.setSenha(dCliente.getSenha());		
		}
		
		private void aplica(ECarrinho eCarrinho, Carrinho dCarrinho) {
			EItemCarrinho eItensCarrinho[] = new EItemCarrinho[0];
			eItensCarrinho = eCarrinho.getItens(eItensCarrinho);
			dCarrinho.setId(eCarrinho.getId());
			if (eCarrinho.getCliente() == null) {
				dCarrinho.setCliente(null);
			} else {
				dCarrinho.setCliente((Cliente) injetor.injetaA(eCarrinho.getCliente()).emB(new Cliente()));
			}
			dCarrinho.removeItens();
			for (int i = 0; i < eItensCarrinho.length; ++i) {
				dCarrinho.adicionaItem((ItemCarrinho) injetor.injetaA(eItensCarrinho[i]).emB(new ItemCarrinho()));
			}
			dCarrinho.setDataUltimaAlteracao(eCarrinho.getDataUltimaAlteracao());
			if (eCarrinho.getPedido() == null) {
				dCarrinho.setPedido(null);
			} else {
				dCarrinho.setPedido((Pedido) injetor.injetaA(eCarrinho.getPedido()).emB(new Pedido()));
			}
		}

		private void aplica(Carrinho dCarrinho, ECarrinho eCarrinho) {
			ItemCarrinho dItensCarrinho[] = new ItemCarrinho[0];
			dItensCarrinho = dCarrinho.getItens(dItensCarrinho);
			eCarrinho.setId(dCarrinho.getId());
			if (dCarrinho.getCliente() == null) {
				eCarrinho.setCliente(null);
			} else {
				eCarrinho.setCliente((ECliente) injetor.injetaA(dCarrinho.getCliente()).emB(new ECliente()));
			}
			eCarrinho.removeItens();
			for (int i = 0; i < dItensCarrinho.length; ++i) {
				eCarrinho.adicionaItem((EItemCarrinho) injetor.injetaA(dItensCarrinho[i]).emB(new EItemCarrinho()));
			}
			eCarrinho.setDataUltimaAlteracao(dCarrinho.getDataUltimaAlteracao());
			if (dCarrinho.getPedido() == null) {
				eCarrinho.setPedido(null);
			} else {
				eCarrinho.setPedido((EPedido) injetor.injetaA(dCarrinho.getPedido()).emB(new EPedido()));
			}
		}
		
		private void aplica(EItemCarrinho eItemCarrinho, ItemCarrinho dItemCarrinho) {
			dItemCarrinho.setId(eItemCarrinho.getId());
			dItemCarrinho.setQtd(eItemCarrinho.getQtd());
			if (eItemCarrinho.getCarrinho() == null) {
				dItemCarrinho.setCarrinho(null);
			} else {
				dItemCarrinho.setCarrinho((Carrinho) injetor.injetaA(eItemCarrinho.getCarrinho()).emB(new Carrinho()));
			}
			if (eItemCarrinho.getProduto() == null) {
				dItemCarrinho.setProduto(null);
			} else {
				dItemCarrinho.setProduto((Produto) injetor.injetaA(eItemCarrinho.getProduto()).emB(new Produto()));
			}
		}

		private void aplica(ItemCarrinho dItemCarrinho, EItemCarrinho eItemCarrinho) {
			eItemCarrinho.setId(dItemCarrinho.getId());
			eItemCarrinho.setQtd(dItemCarrinho.getQtd());
			if (dItemCarrinho.getCarrinho() == null) {
				eItemCarrinho.setCarrinho(null);
			} else {
				eItemCarrinho.setCarrinho((ECarrinho) injetor.injetaA(dItemCarrinho.getCarrinho()).emB(new ECarrinho()));
			}
			if (dItemCarrinho.getProduto() == null) {
				eItemCarrinho.setProduto(null);
			} else {
				eItemCarrinho.setProduto((EProduto) injetor.injetaA(dItemCarrinho.getProduto()).emB(new EProduto()));
			}
		}
		
		private void aplica(EPedido ePedido, Pedido dPedido) {
			dPedido.setId(ePedido.getId());
			dPedido.setValorFrete(ePedido.getValorFrete());
			dPedido.setDataFechamento(ePedido.getDataFechamento());
			dPedido.setDataExpiracao(ePedido.getDataExpiracao());
			if (ePedido.getCarrinho() == null) {
				dPedido.setCarrinho(null);
			} else {
				dPedido.setCarrinho((Carrinho) injetor.injetaA(ePedido.getCarrinho()).emB(new Carrinho()));
			}
		}

		private void aplica(Pedido dPedido, EPedido ePedido) {
			ePedido.setId(dPedido.getId());
			ePedido.setValorFrete(dPedido.getValorFrete());
			ePedido.setDataFechamento(dPedido.getDataFechamento());
			ePedido.setDataExpiracao(dPedido.getDataExpiracao());
			if (dPedido.getCarrinho() == null) {
				ePedido.setCarrinho(null);
			} else {
				ePedido.setCarrinho((ECarrinho) injetor.injetaA(dPedido.getCarrinho()).emB(new ECarrinho()));
			}
		}

		public Object emB(Object objetoB) {
	    	objB = objetoB;
	    	if (injetor.aplicou()) { 
	    		// O injetor encoutrou a aplicação na pilha e reutilizou.
	    	} else if (objA instanceof ESetor && objB instanceof Setor) {
	    		aplica((ESetor) objA, (Setor) objB);
	    	} else if (objA instanceof Setor && objB instanceof ESetor) {
	    		aplica((Setor) objA, (ESetor) objB);
	    	} else if (objA instanceof EProduto && objB instanceof Produto) {
	    		aplica((EProduto) objA, (Produto) objB);
	    	} else if (objA instanceof Produto && objB instanceof EProduto) {
	    		aplica((Produto) objA, (EProduto) objB);
	    	} else if (objA instanceof EUnidadeFederativa && objB instanceof UnidadeFederativa) {
	    		aplica((EUnidadeFederativa) objA, (UnidadeFederativa) objB);
	    	} else if (objA instanceof UnidadeFederativa && objB instanceof EUnidadeFederativa) {
	    		aplica((UnidadeFederativa) objA, (EUnidadeFederativa) objB);
	    	} else if (objA instanceof EMunicipio && objB instanceof Municipio) {
	    		aplica((EMunicipio) objA, (Municipio) objB);
	    	} else if (objA instanceof Municipio && objB instanceof EMunicipio) {
	    		aplica((Municipio) objA, (EMunicipio) objB);
	    	} else if (objA instanceof EEndereco && objB instanceof Endereco) {
	    		aplica((EEndereco) objA, (Endereco) objB);
	    	} else if (objA instanceof Endereco && objB instanceof EEndereco) {
	    		aplica((Endereco) objA, (EEndereco) objB);
	    	} else if (objA instanceof ECliente && objB instanceof Cliente) {
	    		aplica((ECliente) objA, (Cliente) objB);
	    	} else if (objA instanceof Cliente && objB instanceof ECliente) {
	    		aplica((Cliente) objA, (ECliente) objB);
	    	} else if (objA instanceof ECarrinho && objB instanceof Carrinho) {
	    		aplica((ECarrinho) objA, (Carrinho) objB);
	    	} else if (objA instanceof Carrinho && objB instanceof ECarrinho) {
	    		aplica((Carrinho) objA, (ECarrinho) objB);
	    	} else if (objA instanceof EItemCarrinho && objB instanceof ItemCarrinho) {
	    		aplica((EItemCarrinho) objA, (ItemCarrinho) objB);
	    	} else if (objA instanceof ItemCarrinho && objB instanceof EItemCarrinho) {
	    		aplica((ItemCarrinho) objA, (EItemCarrinho) objB);
	    	} else if (objA instanceof EPedido && objB instanceof Pedido) {
	    		aplica((EPedido) objA, (Pedido) objB);
	    	} else if (objA instanceof Pedido && objB instanceof EPedido) {
	    		aplica((Pedido) objA, (EPedido) objB);
	    	} else {
	    		throw new IncompatibleClassChangeError(String.format("Injetor falhou. Tipos (%s -> %s) não compatíveis.", objA.getClass().getSimpleName(), objB.getClass().getSimpleName()));
	    	}
	    	injetor.popInjecao();
	    	return objB; 
	    }
	}
	
	public static class Injetor {
		private Stack<Injecao> pilha;
		private Injecao injecao;
		
		private Injetor() {
			pilha = new Stack<Injecao>();
		}
		
		private boolean aplicou() {
			boolean bAplicou = false;
			if (!pilha.isEmpty()) {
			   Stack<Injecao> stack = new Stack<Injecao>();
			   while (!bAplicou && !pilha.isEmpty()) {
				   stack.push(pilha.pop());
				   if (stack.peek().getObjB().getClass().equals(injecao.getObjB().getClass())) {
					   injecao.objB = stack.peek().getObjB();
					   bAplicou = true;
				   }
			   }
			   while (!stack.isEmpty()) {
				   pilha.push(stack.pop());
			   }
			}
			return bAplicou;
		}
		
		private Injecao popInjecao() {
			injecao = null;
			if (!pilha.isEmpty()) {
				injecao = pilha.pop();
			}
			return injecao;
		}
		
		public Injecao injetaA(Object objA) {
			if (injecao != null) {
				pilha.push(injecao);
			}
			return injecao = new Injecao(this, objA);
		}
	}

	public static Injetor injetor() {
		return new Injetor();
	}
	


    public static SessionFactory getFabricaSessao() {
		if (fabricaSessao == null) {
            // Cria a fábrica de sessão padrão (hibernate.cfg.xml arquivo de configuração) 
			try {
				fabricaSessao = new AnnotationConfiguration().configure().buildSessionFactory();
	        } catch (Throwable ex) {
	            logger.log(Level.SEVERE, null, ex);
	            throw new ExceptionInInitializerError(ex);
	        }
		}
		return fabricaSessao;
	}
	
	public static URL getURLSetorCategoriaProdutoXML() {
		return URLSetorCategoriaProdutoXML;
	}
	
	public void iniciarTarefaImportacaoSetorCategoriaProduto() {
		// TODO: Parametrizar o tempo de intervalo para uma nova importação.
		timer.scheduleAtFixedRate(new SetorCategoriaProdutoImportacaoTarefa(), 5000, 3600000);
	}
	
	public void pararTarefas() {
		timer.cancel();
	}
	
	@Override
	public void contextInitialized(ServletContextEvent sce) {
		try {
			URLSetorCategoriaProdutoXML = new URL(sce.getServletContext().getInitParameter(PARAM_URL_SETOR_CATEGORIA_PRODUTO_XML));
		} catch (MalformedURLException ex) {
            logger.log(Level.SEVERE, null, ex);
            throw new ExceptionInInitializerError(ex);
		}
		getFabricaSessao();
		if (timer == null) {
		  timer = new Timer();
		}
		iniciarTarefaImportacaoSetorCategoriaProduto();
	}

	@Override
	public void contextDestroyed(ServletContextEvent sce) {
		timer.cancel();
		getFabricaSessao().close();
	}


}
